#include "tesselation.h"

// For printing to the console...
#include "states.h"
#include "../console.h"

e3dRenderStates::PrimitiveType
E3D_PrimitiveTypeFromGL (GLenum type)
{
  e3dRenderStates::PrimitiveType primitive_type = e3dRenderStates::Triangles;

  switch (type)
  {
    case GL_TRIANGLE_FAN:
    {
      primitive_type = e3dRenderStates::TriangleFan;
      break;
    }
    case GL_TRIANGLE_STRIP:
    {
      primitive_type = e3dRenderStates::TriangleStrip;
      break;
    }
    case GL_TRIANGLES:
    {
      primitive_type = e3dRenderStates::Triangles;
      break;
    }
    case GL_LINE_LOOP:
    {
      primitive_type = e3dRenderStates::LineLoop;
      break;
    }
    default:
    {
///   eTB_Printf ("Unknown Primitive Type\n");
      break;
    }
  }

  return primitive_type;
}

namespace tess
{
  std::vector<Polygon> inputPolygons;
  std::vector<Polygon> tesselatedPolygons;
  std::deque<Vertex>   inputVertices;

  GLUtesselator* tesselator = NULL;

  bool invalid = false;


  GLUtesselator* newTesselator (void)
  {
    GLUtesselator* tesselator = gluNewTess ();

    if (! tesselator)
    {
      throw std::runtime_error ("Failed creating tesselation object");
    }

    return tesselator;
  }


  void InitTesselator (bool triangles_only)
  {
    if (! tesselator)
      tesselator = newTesselator ();

    gluTessCallback (tesselator, GLU_TESS_BEGIN,     reinterpret_cast< void (CALLBACK *)() >(tesselationBeginPrimitive));
    gluTessCallback (tesselator, GLU_TESS_VERTEX,    reinterpret_cast< void (CALLBACK *)() >(tesselationVertex));
    gluTessCallback (tesselator, GLU_TESS_COMBINE,   reinterpret_cast< void (CALLBACK *)() >(tesselationCombineVertices));
    gluTessCallback (tesselator, GLU_TESS_ERROR,     reinterpret_cast< void (CALLBACK *)() >(tesselationError));

    // Must generate triangles, no triangle fans...
    if (triangles_only)
      gluTessCallback (tesselator, GLU_TESS_EDGE_FLAG, reinterpret_cast< void (CALLBACK *)() >(tesselationEdge));
    else
      gluTessCallback (tesselator, GLU_TESS_EDGE_FLAG, NULL);



    /* To make the tesselator more versatile, turn this into a class and make
       these customizable */

    gluTessProperty (tesselator, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NEGATIVE);
    gluTessProperty (tesselator, GLU_TESS_TOLERANCE,    0.0f);

    gluTessNormal   (tesselator, 0.0f, 0.0f, -1.0f);
  }


  void CALLBACK tesselationBeginPrimitive (GLenum type)
  {
    tesselatedPolygons.push_back (Polygon (E3D_PrimitiveTypeFromGL (type)));
  }

  void CALLBACK tesselationVertex (void* vertexData)
  {
    Vertex* vertex = reinterpret_cast< Vertex* >(vertexData);

    tesselatedPolygons.back ().push_back (*vertex);
  }

  void CALLBACK tesselationCombineVertices (GLdouble coords     [3],
                                            void*    vertexData [4],
                                            GLfloat  weights    [4],
                                            void**   outData)
  {
    inputVertices.push_back (Vertex ((float)coords [0],
                                     (float)coords [1],
                                     (float)coords [2]));

    *outData = &inputVertices.back ();
  }

  void CALLBACK tesselationError (GLenum error)
  {
    std::cout << gluErrorString (error);
  }

  void CALLBACK tesselationEdge (bool edgeFlag)
  {
    bool value = edgeFlag;
  }


  void Begin (void)
  {
    reset ();

    gluTessBeginPolygon (tesselator, NULL);
  }

  bool End (void)
  {
    bool success = true;

    gluTessEndPolygon (tesselator);

    if (tesselatedPolygons.size ()) {
      if (invalid) {
        eTB_Printf ("Tesselated polygon is invalid!\n");
        tesselatedPolygons.back ().clear ();
        success = false;
      }

      /*
      std::copy (tesselatedPolygons.end () - 1,
                   tesselatedPolygons.end (),
                     std::ostream_iterator< tess::Polygon >(std::cout, "\n"));
      */
    } else {
      eTB_Printf ("No polygon generated!\n");
      success = false;
    }

    return success;
  }

  void BeginContour (void)
  {
    gluTessBeginContour (tesselator);

    inputPolygons.push_back (tess::Polygon (LineLoop));

//    std::cout << "Input polygon:\n";
  }

  void EndContour (void)
  {
    gluTessEndContour (tesselator);

//    std::cout << '\n';
  }


  void AddVertex (float x, float y)
  {
    GLdouble vertex [3];
    vertex [2] = 0.0;

    vertex [0] = x;
    vertex [1] = y;

    inputVertices.push_back (Vertex (x, y, 0.0f));
    inputPolygons.back ().push_back (Vertex (x, y, 0.0f));

//    std::cout << "\t{" << x << ", " << y << "}\n";

    gluTessVertex (tesselator, vertex, &inputVertices.back ());
  }


  void Invalidate (void)
  {
    invalid = true;
  }


  void reset (void)
  {
    //gluTessProperty (tesselator, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NEGATIVE);

    invalid = false;
  }

  void Holes (void)
  {
    //gluTessProperty (tesselator, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NEGATIVE);
  }


  std::vector< Vertex >::const_iterator Polygon::begin (void) const
  {
    return vertices_.begin ();
  }

  std::vector< Vertex >::const_iterator Polygon::end (void) const
  {
    return vertices_.end ();
  }

  std::vector< Vertex >::const_reverse_iterator Polygon::rbegin (void) const
  {
    return vertices_.rbegin ();
  }

  std::vector< Vertex >::const_reverse_iterator Polygon::rend (void) const
  {
    return vertices_.rend ();
  }

  Polygon::~Polygon (void)
  {
    clear ();
  }

  void Polygon::push_back (Vertex const& vertex)
  {
    vertices_.push_back (vertex);
  }

  void Polygon::clear (void)
  {
    vertices_.clear ();
  }


  void Polygon::render (void) const
  {
  /*
    glBegin(type_);
      std::for_each(vertices_.begin(), vertices_.end(), std::mem_fun_ref(&Vertex::render));
    glEnd();
    
    if (type_ != GL_LINE_LOOP)
    {
      glColor4f(1, 0, 0, 0.1f);
      glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
      glBegin(type_);
        std::for_each(vertices_.begin(), vertices_.end(), std::mem_fun_ref(&Vertex::render));
      glEnd();
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
      glColor4f(1, 0, 0, 0.5f);
      }
    */
  }



  void Clear (void)
  {
    tesselatedPolygons.clear ();
  }

  std::vector< Polygon >::iterator GetFirst (void)
  {
    return tesselatedPolygons.begin ();
  }

  std::vector< Polygon >::iterator GetLast  (void)
  {
    return tesselatedPolygons.end ();
  }

  std::vector< Polygon >::const_iterator Erase (std::vector< Polygon >::iterator& iter)
  {
    return tesselatedPolygons.erase ((std::vector< Polygon >::iterator) iter);
  }


  template < typename CharT, typename CharTraitsT >
  std::basic_ostream< CharT, CharTraitsT >& operator<<(std::basic_ostream< CharT, CharTraitsT >& stream, Polygon const& polygon)
  {
    stream << "Output Polygon:\n\t";
    switch (polygon.type ())
    {
      case e3dRenderStates::TriangleFan:
      {
        stream << "triangle fan\n";
        break;
      }
      case e3dRenderStates::TriangleStrip:
      {
        stream << "triangle strip\n";
        break;
      }
      case e3dRenderStates::Triangles:
      {
        stream << "triangles\n";
        break;
      }
      case e3dRenderStates::LineLoop:
      {
        stream << "line loop\n";
        break;
      }
    }
    for (std::vector< Vertex >::const_iterator vertex = polygon.begin (), verticesEnd = polygon.end (); vertex != verticesEnd; ++vertex)
    {
      stream << "\t\t" << *vertex << '\n';
    }
    return stream;
  }
};
